package com.ziiwee.reduce.commons.utils;

import com.ziiwee.reduce.commons.interfaces.Children;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author ziiwee
 * @since 2021/2/22
 */
public class TreeUtil {

    private final static Log log = LogFactory.getLog(TreeUtil.class);

    public static <T extends Children<T>> void forEach(List<T> tree, Consumer<T> consumer) {
        for (T t : tree) {
            consumer.accept(t);
            forEach(t.getChildren(), consumer);
        }
    }

    public static <T extends Children<T>> List<T> asChildren(List<Node<T>> list) {
        List<T> tree = new ArrayList<>();
        for (Node<T> node : list) {
            T me = node.getMe();
            me.setChildren(asChildren(node.getChildren()));
            tree.add(me);
        }
        return tree;
    }

    public static <T extends Children<T>> List<T> childrenFormat(List<T> list, Function<T, Object> getId, Function<T, Object> getParentId) {
        List<Node<T>> tree = new ArrayList<>();
        List<Node<T>> notFinds = new ArrayList<>();
        for (T t : list) {
            Object id = getId.apply(t);
            Object parentId = getParentId.apply(t);
            Node<T> node = new Node<>(t, id, parentId);
            if (node.getParentId() == null) {
                tree.add(node);
            } else {
                if (!findParentByMe(tree, node)) {
                    notFinds.add(node);
                }
            }
        }
        findParentByChildren(tree, notFinds);
        return asChildren(tree);
    }

    private static <T> boolean findParentByMe(List<Node<T>> tree, final Node<T> node) {
        tree = tree != null ? tree : new ArrayList<>();
        for (Node<T> parent : tree) {
            if (Objects.equals(node.getParentId(), parent.getId())) {
                parent.getChildren().add(node);
                return true;
            } else {
                if (findParentByMe(parent.getChildren(), node)) {
                    return true;
                }
            }
        }
        return false;
    }

    private static <T> void findParentByChildren(List<Node<T>> tree, List<Node<T>> children) {
        List<Node<T>> notFinds = new ArrayList<>();
        for (Node<T> child : children) {
            if (!findParentByMe(tree, child)) {
                notFinds.add(child);
            }
        }
        if (notFinds.size() > 0 && notFinds.size() != children.size()) {
            findParentByChildren(tree, notFinds);
        }else {
            log.warn("找不到父节点：" + asString(notFinds, it -> String.format("[%s,%s]", it.getParentId(), it.getId())));
        }
    }

    public static <T> String asString(Collection<T> list, Function<T, String> handler) {
        StringBuilder sb = new StringBuilder();
        for (T t : list) {
            if (handler != null) {
                sb.append(handler.apply(t)).append(",");
            } else {
                sb.append(t).append(",");
            }
        }
        String val = sb.toString();
        if (val.length() > 0) {
            val = val.substring(0, val.length() - 1);
        }
        return val;
    }


    @Data
    public static class Node<T> {
        private T me;
        private Object id;
        private Object parentId;
        private Node<T> parent;
        private List<Node<T>> children = new ArrayList<>();

        public Node(T me, Object id, Object parentId) {
            this.me = me;
            this.id = id;
            this.parentId = parentId;
        }
    }


    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Cat {
        private String name;
        private Integer id;
        private Integer parentId;
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class CatImpl extends Cat implements Children<CatImpl> {
        private List<CatImpl> children;
    }

    public static void main(String[] args) {
        List<Cat> list = new ArrayList<>();
        list.add(new Cat("翠翠1", 1, null));
        list.add(new Cat("翠翠1-1", 11, 1));
        list.add(new Cat("翠翠1-2", 12, 1));
        list.add(new Cat("翠翠1-1-1", 111, 11));
        list.add(new Cat("翠翠2", 2, null));
        list.add(new Cat("翠翠21", 21, 2));

        List<CatImpl> collect = list.stream().map(it -> {
            CatImpl cat = new CatImpl();
            BeanUtils.copyProperties(it, cat);
            return cat;
        }).collect(Collectors.toList());

        List<CatImpl> cats = childrenFormat(collect, Cat::getId, Cat::getParentId);

        forEach(cats, it -> {
            System.out.println(it.getName());
        });


    }


}
